// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "MyWidget.h"
#include "vtkCallbackCommand.h"
#include "vtkCommand.h"
#include "vtkEvent.h"
#include "vtkEventData.h"
#include "vtkObjectFactory.h"
// #include "vtkPointHandleRepresentation3D.h"
#include "vtktransformrepresentation3d.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkWidgetCallbackMapper.h"
#include "vtkWidgetEvent.h"
#include "vtkWidgetEventTranslator.h"

#include <algorithm>

VTK_ABI_NAMESPACE_BEGIN
    vtkStandardNewMacro(MyWidget);

//------------------------------------------------------------------------------
MyWidget::MyWidget()
{
    // Set the initial state
    this->WidgetState = MyWidget::Inactive;

    // Okay, define the events for this widget
    this->CallbackMapper->SetCallbackMethod(
        vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, MyWidget::SelectAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
                                            vtkWidgetEvent::EndSelect, this, MyWidget::EndSelectAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonPressEvent,
                                            vtkWidgetEvent::Translate, this, MyWidget::TranslateAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonReleaseEvent,
                                            vtkWidgetEvent::EndTranslate, this, MyWidget::EndSelectAction);
    this->CallbackMapper->SetCallbackMethod(
        vtkCommand::RightButtonPressEvent, vtkWidgetEvent::Scale, this, MyWidget::ScaleAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::RightButtonReleaseEvent,
                                            vtkWidgetEvent::EndScale, this, MyWidget::EndSelectAction);
    this->CallbackMapper->SetCallbackMethod(
        vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, MyWidget::MoveAction);

    {
        vtkNew<vtkEventDataDevice3D> ed;
        ed->SetDevice(vtkEventDataDevice::Any);
        ed->SetInput(vtkEventDataDeviceInput::Any);
        ed->SetAction(vtkEventDataAction::Press);
        this->CallbackMapper->SetCallbackMethod(vtkCommand::Select3DEvent, ed, vtkWidgetEvent::Select3D,
                                                this, MyWidget::SelectAction3D);
    }

    {
        vtkNew<vtkEventDataDevice3D> ed;
        ed->SetDevice(vtkEventDataDevice::Any);
        ed->SetInput(vtkEventDataDeviceInput::Any);
        ed->SetAction(vtkEventDataAction::Release);
        this->CallbackMapper->SetCallbackMethod(vtkCommand::Select3DEvent, ed,
                                                vtkWidgetEvent::EndSelect3D, this, MyWidget::EndSelectAction);
    }

    {
        vtkNew<vtkEventDataDevice3D> ed;
        ed->SetDevice(vtkEventDataDevice::Any);
        ed->SetInput(vtkEventDataDeviceInput::Any);
        this->CallbackMapper->SetCallbackMethod(
            vtkCommand::Move3DEvent, ed, vtkWidgetEvent::Move3D, this, MyWidget::MoveAction3D);
    }
    this->LastDevice = static_cast<int>(vtkEventDataDevice::Any);

    this->ShowInactive = false;
    this->EnableAxisConstraint = 1;
    this->EnableTranslation = 1;
    this->AllowHandleResize = 1;

    this->KeyEventCallbackCommand = vtkCallbackCommand::New();
    this->KeyEventCallbackCommand->SetClientData(this);
    this->KeyEventCallbackCommand->SetCallback(MyWidget::ProcessKeyEvents);
}

//------------------------------------------------------------------------------
MyWidget::~MyWidget()
{
    this->KeyEventCallbackCommand->Delete();
}

//------------------------------------------------------------------------------
void MyWidget::CreateDefaultRepresentation()
{
    if (!this->WidgetRep)
    {
        this->WidgetRep = vtkTransformRepresentation3D::New();
    }
}

//------------------------------------------------------------------------------
void MyWidget::SetCursor(int cState)
{
    if (this->ManagesCursor)
    {
        switch (cState)
        {
        case vtkHandleRepresentation::Outside:
            this->RequestCursorShape(VTK_CURSOR_DEFAULT);
            break;
        default:
            this->RequestCursorShape(VTK_CURSOR_HAND);
        }
    }
}

//------------------------------------------------------------------------------
void MyWidget::SelectAction(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    int X = self->Interactor->GetEventPosition()[0];
    int Y = self->Interactor->GetEventPosition()[1];

    self->WidgetRep->ComputeInteractionState(X, Y);
    if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
    {
        return;
    }

    // We are definitely selected
    if (!self->Parent)
    {
        self->GrabFocus(self->EventCallbackCommand);
    }
    double eventPos[2];
    eventPos[0] = static_cast<double>(X);
    eventPos[1] = static_cast<double>(Y);
    self->WidgetRep->StartWidgetInteraction(eventPos);

    self->WidgetState = MyWidget::Active;
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
        ->SetInteractionState(vtkHandleRepresentation::Selecting);

    MyWidget::GenericAction(self);
}

//------------------------------------------------------------------------------
void MyWidget::SelectAction3D(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    self->WidgetRep->ComputeComplexInteractionState(
        self->Interactor, self, vtkWidgetEvent::Select3D, self->CallData);

    if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
    {
        return;
    }

    self->EventCallbackCommand->SetAbortFlag(1);
    self->WidgetRep->StartComplexInteraction(
        self->Interactor, self, vtkWidgetEvent::Select3D, self->CallData);

    // watch for motion events from this device
    vtkEventData* edata = static_cast<vtkEventData*>(self->CallData);
    vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
    if (!edd)
    {
        return;
    }
    self->LastDevice = static_cast<int>(edd->GetDevice());

    self->WidgetState = MyWidget::Active;
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
        ->SetInteractionState(vtkHandleRepresentation::Selecting);

    MyWidget::GenericAction(self);
}

//------------------------------------------------------------------------------
void MyWidget::TranslateAction(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    double eventPos[2];
    eventPos[0] = static_cast<double>(self->Interactor->GetEventPosition()[0]);
    eventPos[1] = static_cast<double>(self->Interactor->GetEventPosition()[1]);
    self->WidgetRep->StartWidgetInteraction(eventPos);

    if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
    {
        return;
    }

    // We are definitely selected
    self->WidgetState = MyWidget::Active;
    reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
        ->SetInteractionState(vtkHandleRepresentation::Translating);

    MyWidget::GenericAction(self);
}

//------------------------------------------------------------------------------
void MyWidget::ScaleAction(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    if (self->AllowHandleResize)
    {

        double eventPos[2];
        eventPos[0] = static_cast<double>(self->Interactor->GetEventPosition()[0]);
        eventPos[1] = static_cast<double>(self->Interactor->GetEventPosition()[1]);

        self->WidgetRep->StartWidgetInteraction(eventPos);
        if (self->WidgetRep->GetInteractionState() == vtkHandleRepresentation::Outside)
        {
            return;
        }

        // We are definitely selected
        self->WidgetState = MyWidget::Active;
        reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)
            ->SetInteractionState(vtkHandleRepresentation::Scaling);

        MyWidget::GenericAction(self);
    }
}

//------------------------------------------------------------------------------
void MyWidget::GenericAction(MyWidget* self)
{
    // This is redundant but necessary on some systems (windows) because the
    // cursor is switched during OS event processing and reverts to the default
    // cursor.
    self->SetCursor(self->WidgetRep->GetInteractionState());

    // Check to see whether motion is constrained
    if (self->Interactor->GetShiftKey() && self->EnableAxisConstraint)
    {
        reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->ConstrainedOn();
    }
    else
    {
        reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->ConstrainedOff();
    }

    // Highlight as necessary
    self->WidgetRep->Highlight(1);

    self->EventCallbackCommand->SetAbortFlag(1);
    self->StartInteraction();
    self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
    self->Render();
}

//------------------------------------------------------------------------------
void MyWidget::EndSelectAction(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    if (self->WidgetState != MyWidget::Active)
    {
        return;
    }

    // Return state to not selected
    self->WidgetState = MyWidget::Start;
    self->LastDevice = static_cast<int>(vtkEventDataDevice::Any);

    // Highlight as necessary
    self->WidgetRep->Highlight(0);

    // stop adjusting
    if (!self->Parent)
    {
        self->ReleaseFocus();
    }
    self->EventCallbackCommand->SetAbortFlag(1);
    self->EndInteraction();
    self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
    self->WidgetState = MyWidget::Start;
    self->Render();
}

//------------------------------------------------------------------------------
void MyWidget::MoveAction(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    // compute some info we need for all cases
    int X = self->Interactor->GetEventPosition()[0];
    int Y = self->Interactor->GetEventPosition()[1];

    // Set the cursor appropriately
    if (self->WidgetState == MyWidget::Start)
    {
        int state = self->WidgetRep->GetInteractionState();
        self->WidgetRep->ComputeInteractionState(X, Y);
        self->SetCursor(self->WidgetRep->GetInteractionState());
        // Must rerender if we change appearance
        if (reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->GetActiveRepresentation() &&
            state != self->WidgetRep->GetInteractionState())
        {
            self->Render();
        }
        return;
    }

    if (!self->EnableTranslation)
    {
        return;
    }

    // Okay, adjust the representation
    double eventPosition[2];
    eventPosition[0] = static_cast<double>(X);
    eventPosition[1] = static_cast<double>(Y);
    self->WidgetRep->WidgetInteraction(eventPosition);

    // Got this event, we are finished
    self->EventCallbackCommand->SetAbortFlag(1);
    self->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr);
    self->Render();
}

//------------------------------------------------------------------------------
void MyWidget::MoveAction3D(vtkAbstractWidget* w)
{
    MyWidget* self = reinterpret_cast<MyWidget*>(w);

    // watch for motion events from the selected device
    vtkEventData* edata = static_cast<vtkEventData*>(self->CallData);
    vtkEventDataDevice3D* edd = edata->GetAsEventDataDevice3D();
    if (!edd)
    {
        return;
    }
    if (!edd->DeviceMatches(static_cast<vtkEventDataDevice>(self->LastDevice)))
    {
        return;
    }

    // Set the cursor appropriately
    if (self->WidgetState == MyWidget::Start)
    {
        int state = self->WidgetRep->GetInteractionState();
        self->WidgetRep->ComputeComplexInteractionState(
            self->Interactor, self, vtkWidgetEvent::Move3D, self->CallData);

        self->SetCursor(self->WidgetRep->GetInteractionState());

        // Must rerender if we change appearance
        if (reinterpret_cast<vtkHandleRepresentation*>(self->WidgetRep)->GetActiveRepresentation() &&
            state != self->WidgetRep->GetInteractionState())
        {
            self->Render();
        }
        return;
    }

    // Okay, adjust the representation
    self->WidgetRep->ComplexInteraction(
        self->Interactor, self, vtkWidgetEvent::Move3D, self->CallData);

    // Got this event, we are finished
    self->EventCallbackCommand->SetAbortFlag(1);
    self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
    self->Render();
}

//------------------------------------------------------------------------------
void MyWidget::PrintSelf(ostream& os, vtkIndent indent)
{
    // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
    this->Superclass::PrintSelf(os, indent);

    os << indent << "Allow Handle Resize: " << (this->AllowHandleResize ? "On\n" : "Off\n");

    os << indent << "Enable Axis Constraint: " << (this->EnableAxisConstraint ? "On\n" : "Off\n");

    os << indent << "Show Inactive: " << (this->ShowInactive ? "On\n" : "Off\n");
    os << indent << "WidgetState: " << this->WidgetState << endl;
}

//------------------------------------------------------------------------------
void MyWidget::SetEnabled(int enabling)
{
    int enabled = this->Enabled;
    if (this->Enabled == enabling)
    {
        return;
    }
    if (!this->ShowInactive)
    {
        // Forward to superclass
        this->Superclass::SetEnabled(enabling);
        if (enabling)
        {
            this->WidgetState = MyWidget::Start;
        }
        else
        {
            this->WidgetState = MyWidget::Inactive;
        }
    }
    else
    {
        if (enabling) //----------------
        {
            this->Superclass::SetEnabled(enabling);
            this->WidgetState = MyWidget::Start;
        }

        else // disabling------------------
        {
            vtkDebugMacro(<< "Disabling widget");

            this->Enabled = 0;

            // don't listen for events any more
            if (!this->Parent)
            {
                this->Interactor->RemoveObserver(this->EventCallbackCommand);
            }
            else
            {
                this->Parent->RemoveObserver(this->EventCallbackCommand);
            }

            this->WidgetState = MyWidget::Inactive;
            this->InvokeEvent(vtkCommand::DisableEvent, nullptr);
        }
    }

    // We defer enabling the handles until the selection process begins
    if (enabling && !enabled)
    {
        if (this->Parent)
        {
            this->Parent->AddObserver(
                vtkCommand::KeyPressEvent, this->KeyEventCallbackCommand, this->Priority);
            this->Parent->AddObserver(
                vtkCommand::KeyReleaseEvent, this->KeyEventCallbackCommand, this->Priority);
        }
        else
        {
            this->Interactor->AddObserver(
                vtkCommand::KeyPressEvent, this->KeyEventCallbackCommand, this->Priority);
            this->Interactor->AddObserver(
                vtkCommand::KeyReleaseEvent, this->KeyEventCallbackCommand, this->Priority);
        }
    }
    else if (!enabling && enabled)
    {
        if (this->Parent)
        {
            this->Parent->RemoveObserver(this->KeyEventCallbackCommand);
        }
        else
        {
            this->Interactor->RemoveObserver(this->KeyEventCallbackCommand);
        }
    }
}

//------------------------------------------------------------------------------
void MyWidget::ProcessKeyEvents(vtkObject*, unsigned long event, void* clientdata, void*)
{
    MyWidget* self = static_cast<MyWidget*>(clientdata);
    vtkHandleRepresentation* rep = vtkHandleRepresentation::SafeDownCast(self->WidgetRep);
    char* cKeySym = self->Interactor->GetKeySym();
    std::string keySym = cKeySym != nullptr ? cKeySym : "";
    std::transform(keySym.begin(), keySym.end(), keySym.begin(), ::toupper);
    if (event == vtkCommand::KeyPressEvent)
    {
        if (keySym == "X")
        {
            rep->SetXTranslationAxisOn();
        }
        else if (keySym == "Y")
        {
            rep->SetYTranslationAxisOn();
        }
        else if (keySym == "Z")
        {
            rep->SetZTranslationAxisOn();
        }
    }
    else if (event == vtkCommand::KeyReleaseEvent)
    {
        if (keySym == "X" || keySym == "Y" || keySym == "Z")
        {
            rep->SetTranslationAxisOff();
        }
    }
}
VTK_ABI_NAMESPACE_END
